const path = require('path')
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

const only = require('./utils/only')
const Context = require('./context')
const { Controller } = require('./controller')
const requireContext = require('./utils/requireContext')
const Exception = require('./exception')

const getLgger = require('./utils/logger.js')

class OTT extends Koa {
	constructor({
		controllerPath,
		exceptionPath,
		middleWarePath,
		bodyparser = {},
		errorField = { data: null, message: null, code: 500, success: false },
		successField = { data: null, success: true, code: 200 },
		loggerConfig = {}
	}) {
		super()
		this.env = process.env.NODE_ENV
		this.appState = {}
		this.exceptionPath = exceptionPath
		this.controllerPath = controllerPath
		this.middleWarePath = middleWarePath
		this.bodyparser = bodyparser
		this.Exception = Exception
		this.errorField = errorField
		this.successField = successField
		this.loggerConfig = loggerConfig
		this.logger = getLgger('app', this.loggerConfig)
		this.init()
	}
	init() {
		this.logger.info('app init start')
		this.extendContext()
		this.loadException()
		this.laodMiddleWare()
		this.use(
			bodyParser({
				...this.bodyParser
			})
		)
		this.createController()

		this.logger.info('app init success.')
	}
	extendContext() {
		this.use((ctx, next) => {
			new Context(ctx, this)

			return next()
		})
	}
	loadException() {
		this.logger.info('app starting load exceptions...')

		if (this.exceptionPath) {
			let exceptionFiles = requireContext(this.exceptionPath)

			this.logger.info('aexceptionFiles', exceptionFiles)

			exceptionFiles.forEach(file => {
				let exception = require(file)
				Object.keys(exception).forEach(e => (this.Exception[e] = exception[e]))
			})
		}
		this.catchException()
		this.logger.info('app finshed load exceptions.')
	}
	catchException() {
		this.use(async (ctx, next) => {
			let startTime = Date.now()

			try {
				await next()
			} catch (e) {
				if (e instanceof this.Exception) {
					ctx.body = only(Object.assign({}, this.errorField, e), Object.keys(this.errorField))
				} else {
					ctx.body = 'no catch error'
				}
				ctx.logger.debug(e)
			}
			ctx.logger.info(
				`usedtime: ${Date.now() - startTime}ms`,
				`requst: ${ctx.path}`,
				{ params: { ...ctx.params }, query: { ...ctx.query }, body: ctx.request.body },
				'response:',
				ctx.body
			)
		})
	}
	laodMiddleWare() {
		this.logger.info('app starting load middleware...')
		if (this.middleWarePath) {
			let middleWareFiles = requireContext(this.middleWarePath)

			this.logger.info('middleWareFiles', middleWareFiles)

			middleWareFiles
				.map(f => {
					let name = path.relative(this.middleWarePath, f)
					return { name, ...require(f) }
				})
				.filter(({ enable }) => enable)
				.sort((a, b) => a.order - b.order)
				.forEach(({ name, pathMatch, action }) => {
					this.logger.info('use enabled middleware: ', ['pathName', name])

					this.use(async (ctx, next) => {
						if (pathMatch && !pathMatch.test(ctx.path)) return next()
						await action.call(ctx, ctx, next)
					})
				})
		}
		this.logger.info('app finsh load middleware.')
	}
	createController() {
		this.logger.info('app starting load controller...')

		if (!this.controllerPath) throw new Error('options controllerPath can not undefind')
		let controllers = requireContext(this.controllerPath, /\.js$/, true)

		this.logger.info('controllerFiles', controllers)

		controllers.forEach(file => {
			let controllerModle = require(file)
			Object.keys(controllerModle).forEach(m => new controllerModle[m](this))
		})
		this.use(Controller.routes())

		this.logger.info('app finsh loading controller.')
	}
}

exports.OTT = OTT
